#include "../iec60870/apdu.h"
#include "../iec60870/asdu.h"
#include "iec60870.h"
#include "z_memory.h"

static void apdu_fixed(apdu_t *apdu, device_t *device, func_code_e fun) {
    device_session_t *session = (device_session_t *) device->_stat.data;

    apdu->fix.head.bof = APDU_BOF_FIX;

    apdu->fix.body.ctl.dir = control;
    apdu->fix.body.ctl.prm = primary;
    session->fcb = !session->fcb;
    apdu->fix.body.ctl.fcb = session->fcb;
    session->fcv = !session->fcv;
    apdu->fix.body.ctl.fcv = session->fcv;
    apdu->fix.body.addr = device->super.id;
    apdu->fix.body.ctl.fun = fun;

    apdu->fix.tail.cs = 0;
    apdu->fix.tail.cs += apdu->fix.body.ctl._val;
    apdu->fix.tail.cs += apdu->fix.body.addr;
    apdu->fix.tail.eof = APDU_EOF;
}

static void apdu_variable(apdu_t *apdu, device_t *device, func_code_e fun, int asdu_len) {
    int i;
    device_session_t *session = (device_session_t *) device->_stat.data;

    apdu->var.head.bof = apdu->var.head.bof1 = APDU_BOF_VAR;
    apdu->var.head.len = apdu->var.head.len1 = asdu_len + 2;

    apdu->var.body.ctl.dir = control;
    apdu->var.body.ctl.prm = primary;
    session->fcb = !session->fcb;
    apdu->var.body.ctl.fcb = session->fcb;
    session->fcv = !session->fcv;
    apdu->var.body.ctl.fcv = session->fcv;
    apdu->var.body.addr = device->super.id;
    apdu->var.body.ctl.fun = fun;

    apdu->var.p_tail = (apdu_tail_t *) &apdu->var.data[asdu_len];

    apdu->var.p_tail->cs = 0;
    apdu->var.p_tail->cs += apdu->var.body.ctl._val;
    apdu->var.p_tail->cs += apdu->var.body.addr;
    for (i = 0; i < asdu_len; ++i) {
        apdu->var.p_tail->cs += apdu->var.data[i];
    }
    apdu->var.p_tail->eof = APDU_EOF;
}

static apdu_ctrl_t *apdu_ctl(apdu_t *apdu) {
    return APDU_BOF_VAR == apdu->bof ? &apdu->fix.body.ctl : &apdu->var.body.ctl;
}

_Bool apdu_acd(apdu_t *apdu) {
    apdu_ctrl_t *ctl = apdu_ctl(apdu);

    return monitor == ctl->dir && secondary == ctl->prm && ctl->fcb/*acd*/;
}

_Bool apdu_has_data(apdu_t *apdu) {
    apdu_ctrl_t *ctl = apdu_ctl(apdu);

    return monitor == ctl->dir && secondary == ctl->prm && fc_resp_data == ctl->fun;
}

void sched_iec60870_device(channel_t *channel, device_t *device, iec60870_apdu_handler handler) {
    apdu_t apdu = {};

//	bzero(apdu, sizeof(apdu_t));
    do {
        apdu_fixed(&apdu, device, apdu_acd(&apdu) ? fc_req_data_1 : fc_req_data_2);

        if (apdu_write(channel, &apdu) < 0) { return; }

        if (apdu_read(channel, &apdu) < 0) { return; }

        if (APDU_BOF_VAR == apdu.bof && handler) {
            handler(&apdu, channel, device);
        }
    } while (apdu_has_data(&apdu));
}

void sched_iec60870_channel(channel_t *channel, iec60870_apdu_handler handler) {
    int i;

    for (i = 0; i < channel->devices.size; ++i) {
        device_t *device = vector_get(&channel->devices, i);
        sched_iec60870_device(channel, device, handler);
    }
}

void sched_iec60870(channel_t *channel, seqiot_t *seqiot, iec60870_apdu_handler handler) {
    int i;

    //malloc session
    channel->_stat.data = z_calloc(1, sizeof(channel_session_t));
    for (i = 0; i < channel->devices.size; ++i) {
        device_t *device = vector_get(&channel->devices, i);
        device->_stat.data = z_calloc(1, sizeof(device_session_t));
    }

    sched_iec60870_channel(channel, handler);

    //free session
    for (i = 0; i < channel->devices.size; ++i) {
        device_t *device = vector_get(&channel->devices, i);
        z_free(device->_stat.data);
    }
    z_free(channel->_stat.data);
}
